package com.lyncs.ods.modules.msg.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.mail.MailUtil;
import com.alibaba.fastjson.JSON;
import com.lyncs.ods.common.exception.ApiException;
import com.lyncs.ods.common.web.RequestHolder;
import com.lyncs.ods.constant.LyncsOdsConstant;
import com.lyncs.ods.modules.company.model.CompanyInfo;
import com.lyncs.ods.modules.msg.model.MsgSendLog;
import com.lyncs.ods.modules.msg.model.MsgValidateLog;
import com.lyncs.ods.modules.msg.service.MessageService;
import com.lyncs.ods.modules.msg.service.MsgSendLogService;
import com.lyncs.ods.modules.msg.service.MsgValidateLogService;
import com.lyncs.ods.modules.setting.model.ShortLinkInfo;
import com.lyncs.ods.modules.setting.service.ShortLinkInfoService;
import com.lyncs.ods.modules.user.model.UserInfo;
import com.lyncs.ods.modules.user.service.UserInfoService;
import com.lyncs.ods.properties.EmailConfigSetting;
import com.lyncs.ods.properties.SmsConfigSetting;
import com.lyncs.ods.task.CleanTask;
import com.lyncs.ods.utils.AliUtils;
import com.lyncs.ods.utils.SmsUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.util.PropertyPlaceholderHelper;

import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @author alex
 * @date 2022/4/3 23:55
 * @description
 */
@Service
@Slf4j
public class MessageServiceImpl implements MessageService {

    @Autowired
    private MsgValidateLogService msgValidateLogService;
    @Autowired
    private UserInfoService userInfoService;
    @Autowired
    private MsgSendLogService msgSendLogService;
    @Autowired
    private ShortLinkInfoService shortLinkInfoService;

    @Value("${ali.accessKey}")
    private String accessKey;
    @Value("${ali.accessSecret}")
    private String accessSecret;

    PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("{", "}");

    @Override
    public void sendEmailCode(String email) {
        Long userId = RequestHolder.getUserId();
        if (userId == null) {
            throw new ApiException("user not found by email:" + email);
        }
        UserInfo userInfo = userInfoService.getById(userId);
        if (userInfo == null) {
            throw new ApiException("user not found by email:" + email);
        }
        EmailConfigSetting emailConfigSetting = EmailConfigSetting.CODE;
        String code = RandomUtil.randomNumbers(emailConfigSetting.getLength());
        Map<String, String> map = Map.of(
                "code", code,
                "username", userInfo.getUsername(),
                "expiration", emailConfigSetting.getExpiration().toString()
        );
        String content = propertyPlaceholderHelper.replacePlaceholders(getShareHtml(emailConfigSetting.getPath()), map::get);
        Long id = saveMessageLog(userId, null, content, code, LyncsOdsConstant.MessageType.EMAIL.getKey(), emailConfigSetting.getExpiration());
        MailUtil.send(email, emailConfigSetting.getName(), content, true);
        msgValidateLogService.lambdaUpdate().eq(MsgValidateLog::getId, id).set(MsgValidateLog::getStatus, LyncsOdsConstant.MessageStatus.SUCCESS.getKey()).update();
    }

    @Override
    public void sendSmsCode(String phone) {
        SmsConfigSetting smsConfigSetting = SmsConfigSetting.CODE;
        Long userId = RequestHolder.getUserId();
        if (userId == null) {
            throw new ApiException("user not found");
        }
        String code = RandomUtil.randomNumbers(smsConfigSetting.getLength());
        Map<String, String> param = Map.of(
                "code", code,
                "expiration", smsConfigSetting.getExpiration().toString()
        );
        String content = MessageFormat.format(smsConfigSetting.getContent(), code, smsConfigSetting.getLength());

        Long validateLogId = saveMessageLog(userId, null, content, code, LyncsOdsConstant.MessageType.SMS.getKey(), smsConfigSetting.getExpiration());

        Consumer<Long> successFunc = (id) -> updateMessageStatus(id, LyncsOdsConstant.MessageStatus.SUCCESS.getKey());
        Consumer<Long> failedFunc = (id) -> updateMessageStatus(id, LyncsOdsConstant.MessageStatus.FAIL.getKey());
        AliUtils.sendSms(accessKey, accessSecret, phone, smsConfigSetting.getSign(), smsConfigSetting.getTemplateCode(), JSON.toJSONString(param), successFunc, failedFunc, validateLogId);
    }

    @Override
    public Boolean verifySmsCode(String phone, String code) {
        return getLastCode(RequestHolder.getUserId(), LyncsOdsConstant.MessageType.SMS).equals(code);
    }

    @Override
    public Boolean verifyEmailCode(String email, String code) {
        return getLastCode(RequestHolder.getUserId(), LyncsOdsConstant.MessageType.EMAIL).equals(code);
    }

    @Override
    public void shareTicketBySms(Boolean newTicket, LyncsOdsConstant.TxnType type, Long typeId, CompanyInfo companyInfo, Long invitedCompanyId, List<String> tos) {
        if (!newTicket) {
            log.info("订单修改不发送{}短信通知,id:{}", type.getDesc(), typeId);
            return;
        }
        log.info("开始发送{}通知短信,id:{}", type.getDesc(), typeId);
        if (CollectionUtils.isEmpty(tos)) {
            log.error("发送分享短信失败，未配置短信");
            return;
        }
        sendNotifySms(type, typeId, companyInfo.getId(), invitedCompanyId, companyInfo.getShortName(), tos);
    }

    @Override
    public void sendNotifySms(LyncsOdsConstant.TxnType type, Long typeId, Long companyId, Long invitedCompanyId, String companyName, List<String> tos) {
        ShortLinkInfo linkInfo = shortLinkInfoService.generateShortLink(type, typeId, companyId, invitedCompanyId);
        Map<String, String> param = new HashMap<>() {{
            put("companyName", companyName);
            put("ticketType", type.getDesc());
            //put("shareLink", linkInfo.getDeeplink());
            put("code", linkInfo.getKey());
        }};
        SmsConfigSetting smsConfigSetting = SmsConfigSetting.TICKET_CREATE;
        String content = SmsUtils.buildContent(smsConfigSetting, companyName, type.getDesc(), linkInfo.getDeeplink());
        MsgSendLog msgSendLog = new MsgSendLog()
                .setId(IdUtil.getSnowflakeNextId())
                .setType(type.getKey())
                .setCompanyId(companyId)
                .setUserId(0L)
                .setContent(content)
                .setStatus(LyncsOdsConstant.MessageStatus.INIT.getKey())
                .setCreateTime(RequestHolder.getRequestDateTime())
                .setUpdateTime(RequestHolder.getRequestDateTime());
        msgSendLogService.save(msgSendLog);
        AliUtils.sendSms(accessKey, accessSecret, tos.stream().filter(Objects::nonNull).distinct().collect(Collectors.joining(LyncsOdsConstant.Common.COMMA)), smsConfigSetting.getSign(), smsConfigSetting.getTemplateCode(), JSON.toJSONString(param), null, null, null);
    }

    @Override
    public void shareTicketByEmail(Boolean newTicket, LyncsOdsConstant.TxnType type, Long typeId, CompanyInfo companyInfo, CompanyInfo invitedCompanyInfo, List<String> tos) {
        log.info("开始发送{}通知邮件,id:{},tos:{}", type.getDesc(), typeId, tos);
        if (CollectionUtils.isEmpty(tos)) {
            log.error("发送分享邮件失败，未配置邮箱");
            return;
        }
        EmailConfigSetting emailConfigSetting = newTicket ? EmailConfigSetting.TICKET_CREATE : EmailConfigSetting.TICKET_UPDATE;
        String html = getShareHtml(emailConfigSetting.getPath());
        if (StringUtils.isEmpty(html)) {
            log.error("发送分享邮件失败，获取分享页面html源码失败");
            return;
        }
        ShortLinkInfo shareLink = shortLinkInfoService.generateShortLink(type, typeId, companyInfo.getId(), invitedCompanyInfo.getId());
        Map<String, String> param = new HashMap<>() {{
            put("companyLogo", companyInfo.getAvatarUrl() == null ? "https://welynk.cn/images/s/c1.png" : companyInfo.getAvatarUrl());
            put("companyShortName", companyInfo.getShortName());
            put("companyName", companyInfo.getName() == null ? "" : companyInfo.getName());
            put("companySlogan", companyInfo.getSlogan() == null ? "" : companyInfo.getSlogan());
            put("shareLink", shareLink.getDeeplink());
        }};
        String content = propertyPlaceholderHelper.replacePlaceholders(html, param::get);
        MsgSendLog msgSendLog = new MsgSendLog()
                .setId(IdUtil.getSnowflakeNextId())
                .setType(type.getKey())
                .setCompanyId(companyInfo.getId())
                .setUserId(RequestHolder.getUserId())
                .setContent(content)
                .setStatus(LyncsOdsConstant.MessageStatus.INIT.getKey())
                .setCreateTime(RequestHolder.getRequestDateTime())
                .setUpdateTime(RequestHolder.getRequestDateTime());
        msgSendLogService.save(msgSendLog);
//        MailUtil.send(tos, MessageFormat.format(emailConfigSetting.getName(), companyInfo.getShortName(), type.getDesc()), content, true);
        MailUtil.send(tos, String.format(emailConfigSetting.getName(), type.getDesc()), content, true);
    }

    private String getLastCode(Long userId, LyncsOdsConstant.MessageType type) {
        MsgValidateLog one = msgValidateLogService.lambdaQuery().eq(MsgValidateLog::getUserId, userId)
                .eq(MsgValidateLog::getType, type.getKey())
                .orderByDesc(MsgValidateLog::getCreateTime)
                .last(" limit 1")
                .one();
        if (one == null) {
            throw new ApiException("无效验证码");
        }
        if (one.getCount() >= one.getMaxCount()) {
            throw new ApiException("验证码已使用");
        }
        if (one.getStatus().equals(LyncsOdsConstant.MessageStatus.EXPIRED.getKey())) {
            throw new ApiException("验证码已过期");
        }
        if (!one.getStatus().equals(LyncsOdsConstant.MessageStatus.SUCCESS.getKey())) {
            throw new ApiException("无效验证码");
        }
        return one.getCode();
    }

    private void updateMessageStatus(Long id, Integer status) {
        msgValidateLogService.lambdaUpdate().eq(MsgValidateLog::getId, id)
                .set(MsgValidateLog::getStatus, status)
                .set(LyncsOdsConstant.MessageStatus.VERIFIED.getKey().equals(status), MsgValidateLog::getCount, 1)
                .update();
    }

    private Long saveMessageLog(Long userId, Long companyId, String content, String code, Integer type, Integer expiration) {
        LocalDateTime now = RequestHolder.getRequestDateTime();
        MsgValidateLog msgValidateLog = new MsgValidateLog()
                .setId(IdUtil.getSnowflakeNextId())
                .setType(type)
                .setCompanyId(companyId)
                .setUserId(userId)
                .setCode(code)
                .setStatus(LyncsOdsConstant.MessageStatus.INIT.getKey())
                .setCount(0)
                .setMaxCount(1)
                .setExpireTime(LocalDateTime.now().plusMinutes(expiration))
                .setCreateTime(now)
                .setUpdateTime(now);
        MsgSendLog msgSendLog = new MsgSendLog()
                .setId(IdUtil.getSnowflakeNextId())
                .setType(type)
                .setCompanyId(companyId)
                .setUserId(userId)
                .setContent(content)
                .setStatus(LyncsOdsConstant.MessageStatus.INIT.getKey())
                //.setRemark(bizCode)
                .setCreateTime(now)
                .setUpdateTime(now);
        msgValidateLogService.save(msgValidateLog);
        msgSendLogService.save(msgSendLog);
        new Timer().schedule(new CleanTask(msgValidateLog.getId(), msgSendLog.getId(), msgValidateLogService, msgSendLogService), Long.valueOf(expiration) * 60 * 1000);
        return msgValidateLog.getId();
    }

    private String getShareHtml(String path) {
        //String path = isNew ? "email/ods.html" : "email/ods-update.html";
        try {
            ClassPathResource classPathResource = new ClassPathResource(path);
            InputStream inputStream = classPathResource.getInputStream();
            return new String(inputStream.readAllBytes());
        } catch (IOException e) {
            log.error("加载邮件模板失败，path:{}", path);
        }
        return "";
    }
}

